/*
 * File:    ByteString.java
 * Created: 12-Jan-2006
 *
 * by Kenji Hollis <kenji@bitgatesoftware.com>
 * Released under the Creative Commons License version 2.5
 */

package com.bitgate.util.bytes;

import java.util.ArrayList;

/**
 * This class is a byte-based implementation of a simple <code>String</code> class.  It does not implement all of the functionality
 * found in a <code>String</code>, however, it implements enough functionality to allow for 8-bit binary data to be represented
 * and processed as a <code>String</code> object would be.  Since this is an 8-bit clean binary storage unit, it means that no
 * encoding or decoding needs to take place in manipulation of said <code>String</code>.  It further means that the system-wide
 * encoding standard will be completely ignored.  This allows byte-sensitive services such as <b>FTP</b> and <b>SSH</b> to
 * maintain data integrity in either direction.
 * <p/>
 * You can still, however, use standard unicode versions of the returned string if required.  Depending on the system's encoding
 * setting, you can still retrieve the <code>String</code> object using the <code>toString()</code> method.
 *
 * @author Kenji Hollis &lt;kenji@bitgatesoftware.com&gt;
 * @since 0.1
 */
public class ByteString
{
    protected byte buffer[];
    protected int lo, hi;

    /** Carriage Return */
    public static final byte CR = '\r';

    /** Line feed */
    public static final byte LF = '\n';

    /** Space */
    public static final byte SPACE = ' ';

    /** Tab */
    public static final byte TAB = '\t';

    /** Colon */
    public static final byte COLON = ':';

    /** Carriage return + line feed */
    public static final byte[] CRLF = { '\r', '\n' };

    private static final byte UPPER_TO_LOWERCASE = 32;

    /**
     * Bare constructor, constructs a blank string.
     */
    public ByteString()
    {
        this.buffer = (new String("")).getBytes();
        this.lo = 0;
        this.hi = 0;
    }

    /**
     * Creates a copy of the specified string, taking its bytes and storing them.  This will use the system's standard encoding
     * when pulling the bytes from the specified string.
     *
     * @param string The string to copy.
     */
    public ByteString(String string)
    {
        this.buffer = string.getBytes();
        this.lo = 0;
        this.hi = this.buffer.length;
    }

    /**
     * Creates a byte-based string based on the array of bytes specified.
     *
     * @param buffer The array of bytes.
     */
    public ByteString(byte[] buffer)
    {
        this.buffer = buffer;
        this.lo = 0;
        this.hi = this.buffer.length;
    }

    /**
     * Creates a byte-based string based on the array of bytes specified, from a start to an end range.
     *
     * @param buffer The array of bytes.
     * @param lo The start position.
     * @param hi The end position.
     */
    public ByteString(byte[] buffer, int lo, int hi)
    {
        if (lo > hi) {
            throw new IndexOutOfBoundsException("starting index " + lo + " larger than ending index " + hi);
        }

        this.buffer = buffer;
        this.lo = lo;
        this.hi = hi;
    }

    /**
     * Returns a simplified hash of the string.  This is generally used for a <code>HashMap</code> call.
     *
     * @return <code>int</code> containing the hash.
     */
    @Override
    public final int hashCode()
    {
    	return this.toString().hashCode();
    }

    /**
     * Determines whether or not the two <code>ByteString</code> objects are equal.
     *
     * @param that The object to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    @Override
    public final boolean equals(Object that)
    {
		if (this == that) {
		    return true;
		}
		
	    return equals(((ByteString) that).buffer, ((ByteString) that).lo, ((ByteString) that).size());
    }

    /**
     * Returns whether or not the object is equal.
     *
     * @param buf An array of bytes containing a string to compare.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean equals(byte[] buf)
    {
    	return equals(buf, 0, buf.length);
    }

    /**
     * Returns whether or not the object is equal, from a start to an end range.
     *
     * @param buf An array of bytes containing a string to compare.
     * @param low The start range.
     * @param length The length of bytes to check against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean equals(byte[] buf, int low, int length)
    {
    	int lowPosition = low;
    	
		if (this.size() != length) {
		    return false;
		}
	
		for (int i = 0; i < length; i++) {
		    if (this.byteAt(i) != buf[lowPosition++]) {
		    	return false;
		    }
		}
	
		return true;
    }

    /**
     * Checks to see whether or not a string is equal to another string, ignoring the case of the string.
     *
     * @param that The object to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean equalsIgnoreCase(ByteString that)
    {
		if (this == that) {
		    return true;
		}
		
	    return equalsIgnoreCase(that.buffer, that.lo, that.size());
    }

    /**
     * Checks to see whether or not a string is equal to another string, ignoring the case of the string, by an array of bytes.
     *
     * @param buf The buffer to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean equalsIgnoreCase(byte[] buf)
    {
    	return equalsIgnoreCase(buf, 0, buf.length);
    }

    /**
     * Checks to see whether or not a string is equal to another string, ignoring the case of the string, by an array of bytes
     * from start to end.
     *
     * @param buf The buffer to compare against.
     * @param low The start position.
     * @param length The number of bytes to compare.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean equalsIgnoreCase(byte[] buf, int low, int length)
    {
    	int lowCompare = low;
    	
		if (this.size() != length) {
		    return false;
		}
	
		for (int i = 0; i < length; i++) {
		    byte compare = this.byteAt(i);
		    byte compareBuf = buf[lowCompare++];
	
		    if (compare >= 65 && compare <= 90) {
		    	compare += UPPER_TO_LOWERCASE;
		    }
	
		    if (compareBuf >= 65 && compareBuf <= 90) {
		    	compareBuf += UPPER_TO_LOWERCASE;
		    }
		    
		    if (compare != compareBuf) {
		    	return false;
		    }
		}
	
		return true;
    }

    /**
     * Returns whether or not a string is greater or equal to the string being compared to.
     *
     * @param that The <code>ByteString</code> to compare.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean greaterOrEqual(ByteString that)
    {
		int m = this.size();
		int n = that.size();
	
		for(int i = 0; i < n; i++) {
		    if (i >= m) {
		    	return false;
		    }
		    
			byte x = this.byteAt(i);
			byte y = that.byteAt(i);
	
			if (y < x) {
			    return false;
			}
		}
	
		return true;
    }

    /**
     * Returns the number of bytes contained in this <code>ByteString</code> object.
     *
     * @return <code>final int</code> containing the number of bytes.
     */
    public final int size()
    {
    	return (this.hi - this.lo);
    }

    /**
     * Returns the number of bytes contained in this <code>ByteString</code> object.
     *
     * @return <code>final int</code> containing the number of bytes.
     */
    public final int length()
    {
    	return size();
    }

    /**
     * Returns the byte at the specified position.
     *
     * @param i The byte position to look at.
     * @return <code>final byte</code> containing the resulting byte.
     * @throws IndexOutOfBoundsException if the request is out of bounds.
     */
    public final byte byteAt(int i)
    {
		if (this.lo + i >= this.hi) {
		    throw new IndexOutOfBoundsException("request byte " + i + " greater than length " + this.hi);
		}
	
		return this.buffer[this.lo + i];
    }

    /**
     * Determines if the current <code>ByteString</code> object starts with the specified string.
     *
     * @param that The string to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean startsWith(byte[] that)
    {
		if (that.length > this.hi - this.lo) {
		    return false;
		}
	
		for(int i = 0; i < that.length; i++) {
		    char x = (char) (this.buffer[this.lo + i]);
		    char y = (char) (that[i]);
	
		    if (x != y && (x < 'a' || x > 'z' || (x + 'A' - 'a') != y)) {
		    	return false;
		    }
		}
	
		return true;
    }

    /**
     * Determines if the current <code>ByteString</code> object starts with the specified string, ignoring case.
     *
     * @param that The byte array to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean startsWithIgnoreCase(byte[] that)
    {
		if (that.length > this.hi - this.lo) {
		    return false;
		}
	
		for(int i = 0; i < that.length; i++) {
		    byte x = this.buffer[this.lo + i];
		    byte y = that[i];
	
		    if (x >= 65 && x <= 90) {
		    	x += UPPER_TO_LOWERCASE;
		    }
	
		    if (y >= 65 && y <= 90) {
		    	y += UPPER_TO_LOWERCASE;
		    }
	
		    if (x != y) {
		    	return false;
		    }
		}
	
		return true;
    }

    /**
     * Determines if the current <code>ByteString</code> object starts with the specified byte.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean startsWith(byte that)
    {
		byte[] myBuffer = new byte[1];
	
		myBuffer[0] = that;
		return startsWith(myBuffer);
    }

    /**
     * Determines if the current <code>ByteString</code> object starts with the specified byte, ignoring case.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean startsWithIgnoreCase(byte that)
    {
		byte[] myBuffer = new byte[1];
	
		myBuffer[0] = that;
		return startsWithIgnoreCase(myBuffer);
    }

    /**
     * Determines if the current <code>ByteString</code> object starts with the specified <code>String</code>, ignoring case.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean startsWithIgnoreCase(String that)
    {
    	return startsWithIgnoreCase(that.getBytes());
    }

    /**
     * Determines if the current <code>ByteString</code> ends with the specified byte array.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean endsWith(byte[] that)
    {
		if (that.length > this.hi - this.lo) {
		    return false;
		}
	
		int cnt = 0;
	
		for (int i = (this.hi - that.length); i < this.hi; i++) {
		    char x = (char) (this.buffer[i]);
		    char y = (char) (that[cnt++]);
	
		    if (x != y && (x < 'a' || x > 'z' || (x + 'A' - 'a') != y)) {
		    	return false;
		    }
		}
	
		return true;
    }

    /**
     * Determines if the current <code>ByteString</code> ends with the specified byte array, ignoring case.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean endsWithIgnoreCase(byte[] that)
    {
		if (that.length > this.hi - this.lo) {
		    return false;
		}
	
		int cnt = 0;
	
		for(int i = (this.hi - that.length); i < this.hi; i++) {
		    byte x = this.buffer[i];
		    byte y = that[cnt++];
	
		    if (x >= 65 && x <= 90) {
		    	x += UPPER_TO_LOWERCASE;
		    }
	
		    if (y >= 65 && y <= 90) {
		    	y += UPPER_TO_LOWERCASE;
		    }
	
		    if (x != y) {
		    	return false;
		    }
		}
	
		return true;
    }

    /**
     * Determines if the current <code>ByteString</code> ends with the specified byte.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean endsWith(byte that)
    {
		byte[] myBuffer = new byte[1];
	
		myBuffer[0] = that;
		return endsWith(myBuffer);
    }

    /**
     * Determines if the current <code>ByteString</code> ends with the specified byte, ignoring case.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean endsWithIgnoreCase(byte that)
    {
		byte[] myBuffer = new byte[1];
	
		myBuffer[0] = that;
		return endsWithIgnoreCase(myBuffer);
    }

    /**
     * Determines if the current <code>ByteString</code> ends with the specified <code>String</code>, ignoring case.
     *
     * @param that The byte to compare against.
     * @return <code>final boolean</code> containing the result: <code>true</code> if equal, <code>false</code> otherwise.
     */
    public final boolean endsWithIgnoreCase(String that)
    {
    	return endsWithIgnoreCase(that.getBytes());
    }

    /**
     * Determines the index of the first occurance of the specified byte.
     *
     * @param x The byte to compare against.
     * @return <code>final int</code> containing the position of the first occurance of the specified byte.
     */
    public final int indexOf(byte x)
    {
		for(int i = this.lo; i < this.hi; i++) {
		    if (this.buffer[i] == x) {
		    	return (i - this.lo);
		    }
		}
	
		return -1;
    }

    /**
     * Determines the index of the first occurance of the specified byte after the given offset.
     *
     * @param x The byte to compare against.
     * @param offset The offset to start looking from.
     * @return <code>final int</code> containing the position of the first occurance of the specified byte from the given offset.
     */
    public final int indexOf(byte x, int offset)
    {
		for(int i = this.lo + offset + 1; i < this.hi; i++) {
		    if (this.buffer[i] == x) {
		    	return (i - this.lo);
		    }
		}
	
		return -1;
    }

    /**
     * Determines the last index of the specified byte.
     *
     * @param x The byte to compare against.
     * @return <code>final int</code> containing the position of the last occurance of the specified byte.
     */
    public final int lastIndexOf(byte x)
    {
		for(int i = this.hi; --i >= this.lo; ) {
		    if (this.buffer[i] == x) {
		    	return (i - this.lo);
		    }
		}
	
		return -1;
    }

    /**
     * Determines the last index of the specified byte, starting from the offset specified.
     *
     * @param x The byte to compare against.
     * @param offset The offset to start from.
     * @return <code>final int</code> containing the position of the last occurance of the specified byte.
     */
    public final int lastIndexOf(byte x, int offset)
    {
		for(int i = this.lo + offset; --i >= this.lo; ) {
		    if (this.buffer[i] == x) {
		    	return (i - this.lo);
		    }
		}
	
		return -1;
    }

    /**
     * Determines the index of the first occurance of the specified byte array.
     *
     * @param that The byte array to compare.
     * @return <code>final int</code> containing the position of the first occurance of the specified byte.
     */
    public final int indexOf(byte[] that)
    {
    	return indexOf(that, 0);
    }

    /**
     * Determines the index of the first occurance of the specified byte array, from the specified offset.
     *
     * @param that The byte array to compare.
     * @param offset The offset to start from.
     * @return <code>final int</code> containing the position of the first occurance of the specified bytes.
     */
    public final int indexOf(byte[] that, int offset)
    {
		for(int i = this.lo + offset; i <= this.hi - that.length; i++) {
		    int j;
	
		    for(j = 0; j < that.length; j++) {
				char x = (char) (this.buffer[i + j]);
				char y = (char) (that[j]);
		
				if (x != y && (x < 'a' || x > 'z' || (x + 'A' - 'a') != y)) {
				    break;
				}
		    }
	
		    if (j >= that.length) {
		    	return (i - this.lo);
		    }
		}
		
		return -1;
    }

    /**
     * Converts the current <code>ByteString</code> to a <code>String</code> object, and returns it.
     *
     * @return <code>final String</code> containing the result.
     */
    @Override
    public final String toString()
    {
    	return new String(this.buffer, this.lo, this.hi - this.lo);
    }

    /**
     * Returns the byte array stored in the current <code>ByteString</code> object.
     *
     * @return <code>final byte[]</code> containing the result.
     */
    public final byte[] getBytes()
    {
    	return getBytes(true);
    }

    /**
     * Returns the byte array stored in the current <code>ByteString</code> object, with an option to clone the data.
     *
     * @param clone Flag indicating whether or not to clone the result.
     * @return <code>final byte[]</code> containing the result.
     */
    public final byte[] getBytes(boolean clone)
    {
		if (clone) {
		    byte[] result = new byte[this.hi - this.lo];
	
		    System.arraycopy(this.buffer, this.lo, result, 0, this.hi - this.lo);
		    return result;
		}
	
		return this.buffer;
    }

    /**
     * Returns the byte stored in the current <code>ByteString</code> object, with the length of bytes in the result specified
     * by the length given.
     *
     * @param len The length of bytes to return.
     * @return <code>final byte[]</code> containing the results.
     */
    public final byte[] getBytes(int len[])
    {
		len[0] = this.hi;
		return this.buffer;
    }

    /**
     * Returns a substring of the current <code>ByteString</code> object from a start to an end range.
     *
     * @param start The starting position.
     * @param finish The ending position.
     * @return <code>ByteString</code> containing the result.
     * @throws IndexOutOfBoundsException on any errors.
     */
    public ByteString substring(int start, int finish)
    {
		if (start > finish) {
		    throw new IndexOutOfBoundsException("start " + start + " > end " + finish);
		} else if (this.lo + finish > this.hi) {
		    throw new IndexOutOfBoundsException("end " + finish + " > length of string.");
		}
	
		return new ByteString(this.buffer, this.lo + start, this.lo + finish);
    }

    /**
     * Returns a substring of the current <code>ByteString</code> object from the start to the end of the string.
     *
     * @param start The starting position.
     * @return <code>ByteString</code> containing the result.
     * @throws IndexOutOfBoundsException on any errors.
     */
    public ByteString substring(int start)
    {
		if (this.lo + start > this.hi) {
		    throw new IndexOutOfBoundsException("start " + start + " > length of string.");
		}
	
		return new ByteString(this.buffer, this.lo + start, this.hi);
    }

    /**
     * Appends the given array of bytes to the current <code>ByteString</code> object.
     *
     * @param data The byte array to append.
     */
    public void append(byte[] data)
    {
		if (data != null) {
		    byte dataHolder[] = new byte[data.length + this.hi + 1];
	
		    if (this.buffer != null) {
//				System.arraycopy(this.buffer, this.lo, dataHolder, this.lo, this.hi);
				System.arraycopy(this.buffer, this.lo, dataHolder, 0, this.hi);
				System.arraycopy(data, 0, dataHolder, (this.lo + this.hi), data.length);
		
				this.buffer = dataHolder;
				dataHolder = null;
		    } else {
				this.buffer = data;
		    }
	
		    this.hi += data.length;
		}
    }

    /**
     * Appends the given byte to the current <code>ByteString</code> object.
     *
     * @param data The byte array to append.
     */
    public void append(byte data)
    {
		byte dataHolder[] = new byte[1];
	
		dataHolder[0] = data;
		append(dataHolder);
    }

    /**
     * Appends the given array of bytes to the current <code>ByteString</code> object, limiting the number of bytes that
     * are appended.
     *
     * @param data The byte array to append.
     * @param hiLen The number of bytes to append.
     */
    public void append(byte[] data, int hiLen)
    {
		byte dataHolder[] = new byte[hiLen + this.hi + 1];
	
		System.arraycopy(this.buffer, this.lo, dataHolder, this.lo, this.hi);
		System.arraycopy(data, 0, dataHolder, (this.lo + this.hi), hiLen);
	
		this.buffer = dataHolder;
		dataHolder = null;
	
		this.hi += hiLen;
    }

    /**
     * Appends the given <code>ByteString</code> to the current <code>ByteString</code> object.
     *
     * @param data The <code>ByteString</code> object to append.
     */
    public void append(ByteString data)
    {
    	append(data.buffer, data.length());
    }

    /**
     * Appends a string to the current <code>ByteString</code> object.
     *
     * @param data The data to append.
     */
    public void append(String data)
    {
    	append(data.getBytes());
    }

    /**
     * Trims carriage returns and line feeds from the start and end of the current <code>ByteString</code> object.
     *
     * @return <code>ByteString</code> object after trim.
     */
    public ByteString trim()
    {
    	// XXX FIXME:
    	// I feel like the more I do to this fricking function, the worse it gets.  This command is still not
    	// perfectly trimming the string on return, and it's driving me crazy.  I now substitute a "1" for the
    	// length if the trimmed string is 0 length.  I need to fix this.
    	
    	int start = this.lo, end = this.hi;
	
		for(start = this.lo; start < end; start++) {
		    byte entry = this.buffer[start];
	
		    if (entry != CR && entry != LF && entry != SPACE && entry != TAB && entry != 0) {
		    	break;
		    }
		}
	
		for(end = this.hi; end > start; end--) {
		    byte entry = this.buffer[end];
	
		    if (entry != CR && entry != LF && entry != SPACE && entry != TAB && entry != 0) {
		    	break;
		    }
		}
	
		byte newBuffer[] = new byte[(end - start) + 1];
		System.arraycopy(this.buffer, start, newBuffer, 0, (end - start) + 1);
	
		return new ByteString(newBuffer);
    }

    private ByteString trimZero()
    {
		int start = this.lo;
		int end = this.hi;
	
		for(int i = (end - 1); i > start; i--) {
		    if (this.buffer[i] != 0) {
		    	end = (i + 1);
		    	break;
		    }
		}
	
		byte newBuffer[] = new byte[(end - start)];
		System.arraycopy(this.buffer, start, newBuffer, 0, (end - start));
	
		return new ByteString(newBuffer);
    }

    /**
     * Splits the current <code>ByteString</code> based on a pattern of byte array object data, limiting the results.
     *
     * @param that The byte array pattern to split by.
     * @param limit The maximum number of times to split the given string.
     * @return <code>ByteString[]</code> containing the results.
     */
    public ByteString[] split(byte[] that, int limit)
    {
		// XXX: Ugh.  Recode this so that it does not rely on an ArrayList.  That, or save it as a stack, which is more
		// efficient.
    	
		ArrayList<ByteString> entries = new ArrayList<ByteString>();
		int lowerLimit = this.lo;
		int upperLimit = this.hi;
		int lastSeenStart = 0;
		int matches = 0;
	
		for(int start = lowerLimit; start < upperLimit; start++) {
		    boolean found = true;
		    int marker = 0;
	
		    for(marker = 0; marker < that.length; marker++) {
		    	if (this.buffer[start + marker] != that[marker]) {
		    		found = false;
		    		break;
		    	}
		    }
	
		    if (found) {
		    	if (lastSeenStart > 0) {
		    		lastSeenStart--;
		    	}
	
		    	matches++;
	
		    	int foundPosition = start + marker;
		    	int newLength = foundPosition - lastSeenStart;
	
		    	if (limit != -1) {
		    		if (matches >= limit) {
		    			lastSeenStart += that.length;
		    			break;
		    		}
		    	}
		    	
		    	byte[] splitPart = new byte[newLength + 1];
		    	System.arraycopy(this.buffer, lastSeenStart, splitPart, 0, (newLength - 1));
		    	
		    	entries.add((new ByteString(splitPart)).trimZero());
		    	splitPart = null;
		    	
		    	start += marker + 1;
		    	lastSeenStart = start;
		    }
		}
	
		if (lastSeenStart > 0) {
		    lastSeenStart--;
		}
	
		int foundPosition = upperLimit;
		int newLength = (foundPosition - lastSeenStart) + 1;
	
		byte[] splitPart = new byte[newLength + 1];
		System.arraycopy(this.buffer, lastSeenStart, splitPart, 0, (newLength - 1));
	
		entries.add((new ByteString(splitPart)).trimZero());
		ByteString[] entriesArray = new ByteString[entries.size()];
		int i = 0;
	
		for(ByteString bEntry : entries) {
		    entriesArray[i++] = bEntry;
		}
	
		return entriesArray;
    }

    /**
     * Splits the current <code>ByteString</code> by the specified byte array.
     *
     * @param that The byte array pattern to split with.
     * @return <code>ByteString[]</code> containing the results.
     */
    public ByteString[] split(byte[] that)
    {
    	return split(that, -1);
    }
    
    /**
     * Splits the current <code>ByteString</code> by the specified byte array, limiting the results.
     *
     * @param that The byte array pattern to split with.
     * @param limit The maximum number of results.
     * @return <code>ByteString[]</code> containing the results.
     */
    public ByteString[] split(byte that, int limit)
    {
		byte[] myBuffer = new byte[1];
	
		myBuffer[0] = that;
		return split(myBuffer, limit);
    }

    /**
     * Splits the current <code>ByteString</code> by the specified byte array.
     *
     * @param that The byte to split by.
     * @return <code>ByteString[]</code> containing the results.
     */
    public ByteString[] split(byte that)
    {
    	return split(that, -1);
    }
    
    /**
     * Converts the entire <code>ByteString</code> to lower case.
     * 
     * @return <code>ByteString</code> containing all lowercase letters.
     */
    public ByteString toLowerCase()
    {
    	byte[] newData = new byte[this.hi - this.lo];
    	
    	System.arraycopy(buffer, this.lo, newData, 0, this.hi - this.lo);
    	
    	int length = newData.length;
    	
    	for(int i = 0; i < length; i++) {
    		if (newData[i] >= 'A' && newData[i] <= 'Z') {
    			newData[i] += (byte) 32;
    		}
    	}
    	
    	return new ByteString(newData);
    }
}
